/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.xmlparser;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import org.apache.jasper.compiler.Localizer;

/**
 * A simple ASCII byte reader. This is an optimized reader for reading byte streams that only
 * contain 7-bit ASCII characters.
 *
 * @author Andy Clark, IBM
 * @deprecated Will be removed in Tomcat 9.0.x onwards
 */
@Deprecated
public class ASCIIReader extends Reader {

    //
    // Data
    //

    /**
     * Input stream.
     */
    private final InputStream fInputStream;

    /**
     * Byte buffer.
     */
    private final byte[] fBuffer;

    //
    // Constructors
    //

    /**
     * Constructs an ASCII reader from the specified input stream and buffer size.
     *
     * @param inputStream The input stream.
     * @param size The initial buffer size.
     */
    public ASCIIReader(InputStream inputStream, int size) {
        fInputStream = inputStream;
        fBuffer = new byte[size];
    }

    //
    // Reader methods
    //

    /**
     * Read a single character.  This method will block until a character is available, an I/O error
     * occurs, or the end of the stream is reached.
     *
     * <p> Subclasses that intend to support efficient single-character input
     * should override this method.
     *
     * @return The character read, as an integer in the range 0 to 127 (<code>0x00-0x7f</code>), or -1
     * if the end of the stream has been reached
     * @throws IOException If an I/O error occurs
     */
    @Override
    public int read() throws IOException {
        int b0 = fInputStream.read();
        if (b0 > 0x80) {
            throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII", Integer.toString(b0)));
        }
        return b0;
    } // read():int

    /**
     * Read characters into a portion of an array.  This method will block until some input is
     * available, an I/O error occurs, or the end of the stream is reached.
     *
     * @param ch Destination buffer
     * @param offset Offset at which to start storing characters
     * @param length Maximum number of characters to read
     * @return The number of characters read, or -1 if the end of the stream has been reached
     * @throws IOException If an I/O error occurs
     */
    @Override
    public int read(char ch[], int offset, int length) throws IOException {
        if (length > fBuffer.length) {
            length = fBuffer.length;
        }
        int count = fInputStream.read(fBuffer, 0, length);
        for (int i = 0; i < count; i++) {
            int b0 = (0xff & fBuffer[i]); // Convert to unsigned
            if (b0 > 0x80) {
                throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII", Integer.toString(b0)));
            }
            ch[offset + i] = (char) b0;
        }
        return count;
    } // read(char[],int,int)

    /**
     * Skip characters.  This method will block until some characters are available, an I/O error
     * occurs, or the end of the stream is reached.
     *
     * @param n The number of characters to skip
     * @return The number of characters actually skipped
     * @throws IOException If an I/O error occurs
     */
    @Override
    public long skip(long n) throws IOException {
        return fInputStream.skip(n);
    } // skip(long):long

    /**
     * Tell whether this stream is ready to be read.
     *
     * @return True if the next read() is guaranteed not to block for input, false otherwise.  Note
     * that returning false does not guarantee that the next read will block.
     * @throws IOException If an I/O error occurs
     */
    @Override
    public boolean ready() throws IOException {
        return false;
    } // ready()

    /**
     * Tell whether this stream supports the mark() operation.
     */
    @Override
    public boolean markSupported() {
        return fInputStream.markSupported();
    } // markSupported()

    /**
     * Mark the present position in the stream.  Subsequent calls to reset() will attempt to
     * reposition the stream to this point.  Not all character-input streams support the mark()
     * operation.
     *
     * @param readAheadLimit Limit on the number of characters that may be read while still preserving
     * the mark.  After reading this many characters, attempting to reset the stream may fail.
     * @throws IOException If the stream does not support mark(), or if some other I/O error occurs
     */
    @Override
    public void mark(int readAheadLimit) throws IOException {
        fInputStream.mark(readAheadLimit);
    } // mark(int)

    /**
     * Reset the stream.  If the stream has been marked, then attempt to reposition it at the mark.
     * If the stream has not been marked, then attempt to reset it in some way appropriate to the
     * particular stream, for example by repositioning it to its starting point.  Not all
     * character-input streams support the reset() operation, and some support reset() without
     * supporting mark().
     *
     * @throws IOException If the stream has not been marked, or if the mark has been invalidated, or
     * if the stream does not support reset(), or if some other I/O error occurs
     */
    @Override
    public void reset() throws IOException {
        fInputStream.reset();
    } // reset()

    /**
     * Close the stream.  Once a stream has been closed, further read(), ready(), mark(), or reset()
     * invocations will throw an IOException. Closing a previously-closed stream, however, has no
     * effect.
     *
     * @throws IOException If an I/O error occurs
     */
    @Override
    public void close() throws IOException {
        fInputStream.close();
    } // close()

} // class ASCIIReader
